package com.wontube.core.pub.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

import javax.servlet.AsyncContext;
import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.wontube.client.extension.utils.StringUtils;
import com.wontube.client.io.IObjectProtocol;
import com.wontube.client.io.ProtocolProxyFactory;
import com.wontube.client.modle.vo.ClientSession;
import com.wontube.client.pub.ProxyRequest;
import com.wontube.client.pub.RequestHeaders;
import com.wontube.client.pub.ServerResponse;
import com.wontube.core.annotation.CacheSupport;
import com.wontube.core.cache.CacheConf;
import com.wontube.core.cache.Cacher;
import com.wontube.core.cache.EHCacheKey;
import com.wontube.core.conf.Domain;
import com.wontube.core.pub.AppEnvironment;
import com.wontube.core.pub.IUserInfo;
import com.wontube.core.pub.ServerContext;
import com.wontube.core.pub.SessionManager;
import com.wontube.core.pub.servlet.handlers.HandlerConfig;
import com.wontube.core.pub.servlet.handlers.HandlerWrapper;
import com.wontube.core.pub.servlet.handlers.MethodWrapper;
import com.wontube.core.remote.IHandler;

/**
 * @author yuechao
 * 
 */
public abstract class PostProcessor {	
	protected final Logger logger = LoggerFactory.getLogger(this.getClass());

	protected HttpServletRequest request;
	protected PrintWriter writer;
	private AsyncContext asyncContext;
	protected boolean isAnsyncClosed = false;

	private final ArrayList<HttpAsyncListener> listeners = new ArrayList<HttpAsyncListener>();

	public PostProcessor() {
	}

	public synchronized boolean isAnsyncClosed() {
		return this.isAnsyncClosed;
	}

	public synchronized void setAnsyncClosed(boolean value) {
		this.isAnsyncClosed = value;
	}

	public HttpServletRequest getRequest() {
		return request;
	}

	public void setRequest(HttpServletRequest request) {
		this.request = request;
	}

	public PrintWriter getWriter() {
		return writer;
	}

	public void setWriter(PrintWriter writer) {
		this.writer = writer;
	}

	public AsyncContext getAsyncContext() {
		return this.asyncContext;
	}

	public void setAsyncContext(AsyncContext value) {
		this.asyncContext = value;
	}

	public void addListener(HttpAsyncListener listener) {
		this.listeners.add(listener);
	}

	public void removeListener(HttpAsyncListener listener) {
		this.listeners.remove(listener);
	}
	
	protected abstract ProxyRequest readProxyRequest();

	protected abstract void onResponse(ServerResponse result);

	public void process() throws IOException {
		try {
			this.internalProcess();
		} finally {
			for (HttpAsyncListener listener : this.listeners) {
				listener.onFinished();
			}
		}
	}

	private void internalProcess() throws IOException {
		// 实际请求
		ProxyRequest proxyRequest = this.readProxyRequest();
		if (proxyRequest == null) {
			return;
		}
		
		RequestHeaders header = proxyRequest.getHeaders();		
		IHandler handler = this.findHandler(header);
		if (handler == null) {
			return;
		}		
		
		String className = header.getClazzName();
		String methodName = header.getMethodName();		
		String agent = header.getAgent();
		
		String[] params = proxyRequest.getArguments();
		if(params == null){
			params = new String[0];
			proxyRequest.setArguments(params);
		}
		
		// 获取方法, 有缓存
		MethodWrapper methodWrapper = handler.getMethod(methodName, params.length);
		if (methodWrapper == null) {			
			String msg = StringUtils.format("unknown remote method [{}] with [{}] parameters.", methodName, params.length);
			this.onError(header.getSessionId(), msg);
			
			msg += ", from Agent:" + agent;
			logger.error(msg);				
			return;
		}
		
		String dataFormat = header.getDataFormat();
		IObjectProtocol protocol = ProtocolProxyFactory.createProtocol(dataFormat);
		if (protocol == null) {
			this.onError(header.getSessionId(), "Not supported data format: " + dataFormat);
			return;
		}

		// 设置上下文, 每一个请求有一个上下文，处理完成后会释放
		ServerContext ctx = handler.getContext();
		ctx.setRequest((HttpServletRequest) request);
		ctx.setProcessor(this);
		ctx.setMethodWrapper(methodWrapper);

		ClientSession session = SessionManager.manager.getSession(header.getSessionId(), true);
		String apiVersion = header.getApiVersion();
		if ("1.0".equals(apiVersion)) { // 兼容旧版本
			final String session10 = "session1.0";
			proxyRequest.getHeaders().setSessionId(session10);
			session.setSessionId(session10);
		}
		
		// update session with request header
		session.updateSession(header);

		String remoteIp = header.getExtHeaders().get("X-Real-IP");
		if (remoteIp == null) {
			// 如果使用nginx反向代理，使用这个
			remoteIp = request.getHeader("X-Real-IP");
			if (StringUtils.isNullOrEmpty(remoteIp) || "unknown".equalsIgnoreCase(remoteIp)) {
				remoteIp = request.getRemoteAddr();
			}
		}
		
		session.setLastClientIP(remoteIp);
		ctx.setSession(session);
		SessionManager.manager.putSession(session.getSessionId(), session);

		IUserInfo user = session.<IUserInfo>getProperty("user");
		if (user != null) {
			String[] args = new String[] { agent, user.getUserName(), remoteIp };
			logger.debug("loginned user: {}.{}@{}", args);
		} else {
			logger.debug("unknown user from {}.{}", agent, remoteIp);
		}

		logger.debug("begin to call {}.{}", className, methodName);

		// 返回response
		ServerResponse result = new ServerResponse();
		try {			
			if ( ! this.execute(protocol, ctx, proxyRequest)) {
				this.onError(header.getSessionId(), ctx.getError());
				logger.warn("fail to call {}.{}, massage: {}", new Object[]{	className, methodName, ctx.getError()});					
				return;
			}

			result.setDone(true);
			result.setSessionId(session.getSessionId());
			
			if (ctx.getResponse() != null) {
				logger.debug("serializing");

				String content = protocol.toProtoString(ctx.getResponse());
				result.setContent(content);

				logger.debug("serialize done, content length: {}", result.getContent().length());

				// 把自己的地址传出去，用于集群下载
				String who = Domain.defaultDomain.getWho();
				String innerWho = Domain.defaultDomain.getInnerWho();
				result.getHeaders().put(Domain.domainKey, who);
				result.getHeaders().put(Domain.innerDomainKey, innerWho);
			}

			this.onResponse(result);
			logger.debug("response done");
		} catch (InvocationTargetException ex) {
			this.onError(session.getSessionId(), ex.getCause());
			logger.error("invocation target exception happen, the client info is: {}.", proxyRequest.toDebugString(remoteIp), ex);
		} catch (Exception ex) {
			this.onError(session.getSessionId(), ex);
			logger.error("error happen, the client info is {}.", proxyRequest.toDebugString(remoteIp), ex);
		}
	}

	private IHandler findHandler(RequestHeaders header) {
		// 找到请求的类
		String clazzName = header.getClazzName();
		if (clazzName == null || "".equals(clazzName)) {
			String msg = "clazzName cannot be null.";
			this.onError(header.getSessionId(), msg);
			logger.error(msg);
			return null;
		}

		HandlerWrapper handlerWrapper = AppEnvironment.handlerMap.get(clazzName);
		if (handlerWrapper == null) {
			String msg = "unknown remote class: " + clazzName;
			this.onError(header.getSessionId(), msg);
			
			msg += ", from Agent:{}";
			logger.error(msg, header.getAgent());
			return null;
		}

		return handlerWrapper.getHandler();
	}

	private boolean execute(IObjectProtocol protocol, ServerContext ctx, ProxyRequest proxyRequest) throws Exception {
		// 确定参数
		String[] args = proxyRequest.getArguments();
		int argSize = args.length;
		MethodWrapper methodWrapper = ctx.getMethodWrapper();
		HandlerConfig config = methodWrapper.getConfig();		
		Method method = methodWrapper.getMethod();
		IHandler handler = config.getHandler();
		Object[] arguments = new Object[argSize];
		Class<?>[] parameterTypes = method.getParameterTypes();
		
		for (int i = 0; i < argSize; i++) {
			String arg = args[i];
			arguments[i] = protocol.toObject(arg, parameterTypes[i]);
		}

		try {
			// 开始事务
			if ( ! handler.begin(config)) {
				// 插件阻止执行
				return false;
			}

			Object serverResponse = null;
			CacheSupport support = method.getAnnotation(CacheSupport.class);
			if (support != null) { // 检查缓存
				RequestHeaders header = proxyRequest.getHeaders();
				logger.debug("has cache support, begin to get and cache result");
				String cacheName = header.getClazzName() + "." + header.getMethodName() + "." + argSize;
				EHCacheKey key = new EHCacheKey(arguments);
				if (support.isAgentRelated()) {
					key.setAgent(ctx.getSession().getLastAgent());
				}
				if (support.isSessionRelated()) {
					key.setSessionId(ctx.getSession().getSessionId());
				}
				CacheConf conf = new CacheConf(support.timeToLiveSeconds(), support.timeToIdleSeconds());
				serverResponse = Cacher.defaultCacher.getAndCache(cacheName, key, conf, handler, method);
			} else {
				// 处理业务逻辑
				logger.debug("no cache support, invoke method directly.");
				serverResponse = method.invoke(handler, arguments);
			}
	
			ctx.setResponse(serverResponse);
			if (ctx.isCancel()) { // 用户取消
				return false;
			}

			// 一切正常，提交事务
			handler.commit(config);

		} finally {
			handler.end(config);
		}

		return true;
	}

	/**
	 * 返回服务器错误
	 * 
	 * @param response
	 * @param msg
	 */
	private void onError(String sessionId, String msg) {
		if (StringUtils.isNullOrEmpty(msg)) {
			msg = "Unknown Error";
		}

		ServerResponse result = new ServerResponse();
		result.setDone(false);
		result.setSessionId(sessionId);
		result.setContent(msg);

		this.onResponse(result);
	}

	/**
	 * 返回服务器错误
	 * 
	 * @param response
	 * @param ex
	 * @throws IOException
	 */
	private void onError(String sessionId, Throwable ex) throws IOException {
		ServerResponse result = new ServerResponse();
		result.setDone(false);
		Throwable t = ex;
		Throwable prevEx = ex;
		while (t != null) {
			prevEx = t;
			t = t.getCause();
		}

		String msg = prevEx.getMessage();
		// 如果没有定义异常信息，直接输出异常堆栈信息
		if (StringUtils.isNullOrEmpty(msg)) {
			msg = ex.toString();
		}
		this.onError(sessionId, msg);
	}
}
